<template>
  <div>
    <h3>Vue3 + TS</h3>
    <p>简单类型</p>
    <ul>
      <li>{{ num }}</li>
      <li>{{ str }}</li>
    </ul>
    <p>复杂类型</p>
    <ul>
      <li v-for="item in test1" :key="item.id">{{ item.name }}</li>
    </ul>
    <div>通过模拟发请求得来数据</div>
    <ul>
      <li v-for="item in test2" :key="item.id">{{ item.name }}</li>
    </ul>
    <div>使用 computed：</div>
    <ul>
      <li v-for="item in test3" :key="item.id">{{ item.name }}</li>
    </ul>

    <!-- 父子通信 -->
    <h3>父子通信</h3>
    <FatherSon
      :sonMoney="sonMoney"
      @changeSonMoney="changeSonMoney"
      :sonPerson="sonPerson"
      @changeSonPerson="changeSonPerson"
      @changeAll="changeAll"
    />

    <!-- 事件处理与 TS -->
    <h3>事件处理与 TS</h3>
    <input type="text" @change="handleInput">

    <!-- 获取 dom 对象 -->
    <h3>获取 dom 对象</h3>
    <input type="text" ref="getInputDom">

    <!-- 类型声明文件 -->
    <!-- 
      项目中安装的第三方库里面都是打包后的 js 代码，但是我们使用的时候却有对应的 TS 类型提示
      是因为带三方库中的 js 代码都有对应的 TS 类型声明文件
      通俗来讲：在 ts 中以 .d.ts 为后缀的文件，称之为 ts 的类型声明文件，主要作用是描述 js 模块中所有导出成员的类型信息

      在之前的学习中，我们将定义的类型都写在 script 中，但是在实际开发中，我们会将所有定义的类型全部放置在一个文件
      在 src 下新建一个 types 文件夹，在里面新建一个 data.d.ts 作为类型声明文件
      使用时导入即可： import type { 定义的类型 } from '文件位置'
      备注：可以导入时加一个 type ，为了语义化，当然不加也不会报错
    -->
    <h3>类型声明文件</h3>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import FatherSon from './components/father_son.vue'

// 导入类型声明文件（可以加一个 type ，语义化，当然不加也不会报错）
import type { testType } from '../types/data'

// 说明：ref 是泛型函数，可以接收类型参数
// 简单类型（可以不用一个个写类型，使用类型推断就可以，能不写就不写）：
const num = ref(10)
const str = ref('马飞')

// 复杂类型：
const arr1 = ref<number[]>([1, 4])
const obj1 = ref<{ name: string, age: number }>({ name: '马飞', age: 12 })

// ts 的类型推断可以自主推断出类型
const test1 = ref([
  {
    id: 1,
    name: '111',
    log: () => {
      console.log('正在学习');
    }
  },
  {
    id: 2,
    name: '222',
    log: () => {
      console.log('正在嗨皮');
    }
  }
])

// 但是实际开发中，该数据一般是从后台发请求获取而来，因此需要加类型
type Type1 = {
  id: number
  name: string
  log: () => void
}[]
const test2 = ref<Type1>([])
setTimeout(() => {
  test2.value = [
    {
      id: 1,
      name: '正在学习',
      log: () => {
        console.log('正在学习');
      }
    },
    {
      id: 2,
      name: '正在嗨皮',
      log: () => {
        console.log('正在嗨皮');
      }
    }
  ]
}, 2000)

// 使用 computed
// 以下是完整写法，但是可以不用写类型（和 js 写法一样即可）
const test3 = computed<Type1>(() => {
  return test2.value.filter(item => {
    return item.id === 2
  })
})

// 使用父子通信
// defineProps 也是个泛型函数，可以通过传递泛型变量指定 props 的类型
// 子组件中写法：
// const props = defineProps<{
//   sonMoney: number
// }>()
const sonMoney = ref(1000)
const sonPerson = ref('马飞')

// 使用 defineEmits
// 子组件中写法见子组件中
const changeSonMoney = (val: number) => {
  sonMoney.value = val
}
const changeSonPerson = (val: string) => {
  sonPerson.value = val
}
const changeAll = (val1: number, val2: string) => {
  sonMoney.value = val1
  sonPerson.value = val2
}

// 事件处理与 TS
// 1、可以通过 $event 查看事件对象的类型：@change="handleInput($event)"
// 直接 e.target.value 是没有代码提示的，因此记得类型断言
// 2、在控制台通过 document.querySelector('input').__proto__ 可以获取某个 html 元素类型
const handleInput = (e: Event) => {
  console.log('获取输入框的值：', (e.target as HTMLInputElement).value)
}

// 获取 dom 对象
const getInputDom = ref<HTMLInputElement>()
// 操作 dom 需要在 onMounted 中执行
onMounted(() => {
  // 获取焦点
  getInputDom.value?.focus()
})

// 类型声明文件
// 在之前的学习中，我们将定义的类型都写在 script 中，但是在实际开发中，我们会将所有定义的类型全部放置在一个文件
// 在 src 下新建一个 types 文件夹，在里面新建一个 data.d.ts 作为类型声明文件
const testObj = ref<testType>({
  name: '马飞',
  age: 11
})
</script>

<style scoped></style>